#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <sstream>
#include <fstream>
#include <functional>
#include "Log.hpp"
#include<time.h>
#include<cstdio>

static const std::string sep = "\r\n";
static const std::string header_sep = ": ";
static const std::string wwwroot = "wwwroot";
static const std::string homepage = "index.html";
static const std::string filesuffixsep = ".";
static const std::string space = " ";
static const std::string httpversion = "HTTP/1.0";
static const std::string args_sep = "?";

class HttpRequest;
class HttpResponse;
// 当GET、POST方法请求带了参数时，就不能走常规构建response进行返回，而是根据请求什么Path资源回调对应的业务
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>;

// Request
// http内部，要根据目标要访问的 资源的文件后缀，区分清楚文件类型，同过Content-Type告诉浏览器，我的response的正文的 类型！
class HttpRequest
{
private:
    // 获取请求每一行
    std::string GetOneLine(std::string &req)
    {
        if (req.empty())
            return req;
        auto pos = req.find(sep);
        if (pos == std::string::npos)
            return std::string(); // 返回空行，是20%小概率没有读到完整报文导致的，我们这里不管
        // 提取一行  //假设发过来的都是完整报文，我们都是基于理想的状态下，所截取出行的
        std::string line = req.substr(0, pos);
        // 提取完一行之后，把报文里面提取掉的删掉去
        req.erase(0, pos + sep.size());   // 还有"\r\n"一并去除
        return line.empty() ? sep : line; // 如果提取出空，说明该行是空行，否则是其他行返回
    }

    // 解析请求行
    bool ParseReqLine()
    {
        if (_req_line.empty())
            return false;
        // 请求方法  url   version  _path
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _version;

        // //login?use=zhangsan&passwd=123456
        // 如果是GET方法，我们就判断一下是否有args，有就从_url中提取出来
        if (strcasecmp("get", _method.c_str()) == 0)
        {
            auto pos = _url.find(args_sep);
            if (pos != std::string::npos)
            {
                LOG(INFO, "change begin, url: %s\n", _url.c_str());
                _args = _url.substr(pos + args_sep.size());
                _url.resize(pos);
                LOG(INFO, "chage done, url: %s, _args: %s\n", _url.c_str(), _args.c_str());
            }
        }

        _path += _url;

        LOG(DEBUG, "url: %s\n", _url.c_str());
        // 如果请求的是根目录， 转成web目录下的首页
        if (_path[_path.size() - 1] == '/')
        {
            _path += homepage;
        }

        // 请求资源的后缀获取到
        auto pos = _path.rfind(filesuffixsep);
        if (pos == std::string::npos)
            _suffix = ".unknown"; // 当解析不到文件后缀类型，默认认为是.html类型文件后缀
        else
            _suffix = _path.substr(pos); // 否则从点开始一直到最后都是，文件后缀

        LOG(INFO, "client wang get %s, _suffix: %s\n", _path.c_str(), _suffix.c_str());
        return true;
    }

    // 解析每行报头
    bool ParseHeaderHelper(const std::string &header, std::string *K, std::string *V)
    {
        auto pos = header.find(header_sep);
        if (pos == std::string::npos)
            return false;
        *K = header.substr(0, pos);
        *V = header.substr(pos + header_sep.size());
        return true;
    }

    // 解析请求报文
    bool ParseHeader()
    {
        for (auto &header : _req_header)
        {
            std::string K, V;
            if (ParseHeaderHelper(header, &K, &V))
                _headers.insert({K, V});
        }
        return true;
    }

public:
    HttpRequest()
        : _blank_line(sep), _path(wwwroot)
    {
    }
    ~HttpRequest()
    {
    }
    // 序列化和反序列化
    void Serialize()
    {
    }
    // 用浏览器充当客户端，所以客户端不用我们写，但是服务端拿到Request必须反序列化
    void Deserialize(std::string &req)
    {
        _req_line = GetOneLine(req);
        // while循环不断提取报头内容，直到提取到空行，之后就是请求正文
        while (true)
        {
            std::string header_line = GetOneLine(req);
            if (header_line.empty())
                break;
            else if (header_line == sep)
            {
                _req_text = req;
                break; // 提取到空行也结束了
            }
            else
                _req_header.emplace_back(header_line);
        }

        // 反序列这里一并将解析出的期望协议一并做掉
        ParseReqLine();
        ParseHeader();
    }

    // 获取请求的文件资源路径
    std::string Path()
    {
        return _path;
    }

    // 获取文件后缀
    std::string Suffix()
    {
        return _suffix;
    }
    // 获取请求正文
    std::string Text()
    {
        return _req_text;
    }
    // 获取请求方法
    std::string Method()
    {
        return _method;
    }
    // 获取args
    std::string Args()
    {
        return _args;
    }
    // 判断是否带参数
    bool IsExec()
    {
        return !_args.empty() || !_req_text.empty();
    }
    // DEBUG
    void Print()
    {
        // DEBUG 一下原始协议
        std::cout << "===" << _req_line << std::endl;
        for (auto &header : _req_header)
        {
            std::cout << "***" << header << std::endl;
        }
        std::cout << _blank_line;
        std::cout << _req_text << std::endl;

        // DEBUG 一下，期望看到的协议内容
        std::cout << "method ###" << _method << std::endl;
        std::cout << "url ###" << _url << std::endl;
        std::cout << "path ###" << _path << std::endl;
        std::cout << "httpverion ### " << _version << std::endl;
        for (auto &header : _headers)
        {
            std::cout << "@@@" << header.first << " - " << header.second << std::endl;
        }
    }

private:
    // 反序列化后   原始协议
    std::string _req_line;                // 请求行
    std::vector<std::string> _req_header; // 请求报头每一行内容存储到vector
    std::string _blank_line;              // 空行
    std::string _req_text;                // 请求正文    POST方法会将参数放到请求正文中

    // 期望解析协议
    std::string _method;                                   // 请求方法
    std::string _url;                                      // 请求的资源路径
    std::string _path;                                     // 将请求资源拼上我们的web目录，最终定位到打开资源的文件
    std::string _version;                                  // 请求服务器版本
    std::string _args;                                     // GET方法会将参数接到_url后
    std::string _suffix;                                   // 文件后缀类型 例如 .html
    std::unordered_map<std::string, std::string> _headers; // 请求报文内容 k: V
};

// Response
class HttpResponse
{
public:
    HttpResponse() : _blank_line(sep), _version(httpversion)
    {
    }
    ~HttpResponse()
    {
    }

    // 序列化和反序列化
    // 用浏览器充当客户端,所以客户端不用我们写，但是服务端拿到Response必须进行序列化发回浏览器
    std::string Serialize()
    {
        // 序列化根据给定字段，开始构建状态行等等
        _status_line = _version + space + std::to_string(_code) + space + _desc + sep; // 状态行
        // 报文头
        for (auto &header : _headers)
        {
            _resp_header.emplace_back(header.first + header_sep + header.second + sep);
        }
        // 序列化返回
        std::string resp = _status_line;
        for (auto &header : _resp_header)
        {
            resp += header;
        }
        resp += _blank_line; // 加空行
        resp += _resp_text;  // 加正文
        return resp;
    }
    void Deserialize()
    {
    }
    // 添加状态行 status_line        只给状态码，即可，状态描述和版本写死了
    void AddStatusLine(int code, const std::string &desc)
    {
        _code = code;
        _desc = desc; // ToDo
    }
    // 添加报头行
    void AddHeaderLine(const std::string &K, const std::string &V)
    {
        LOG(DEBUG, "AddHeader: %s->%s\n", K.c_str(), V.c_str());
        _headers[K] = V;
    }
    // 添加应答正文
    void AddText(const std::string &text)
    {
        _resp_text = text;
    }

private:
    // 构建Response所需要的字段
    std::string _status_line;              // 状态行
    std::vector<std::string> _resp_header; //  报头内容
    std::string _blank_line;               // 空行
    std::string _resp_text;                // 应答正文

    int _code;                                             // 状态码
    std::string _desc;                                     // 状态描述
    std::string _version;                                  // 请求服务器版本
    std::unordered_map<std::string, std::string> _headers; // 应答报文内容 k: V
};

class Factory
{
public:
    static std::shared_ptr<HttpRequest> BuildHttpRequest()
    {
        return std::make_shared<HttpRequest>();
    }
    static std::shared_ptr<HttpResponse> BuildHttpResponse()
    {
        return std::make_shared<HttpResponse>();
    }

private:
};

class HttpServer
{
public:
    HttpServer()
    {
        _mime_type.insert(std::make_pair(".html", "text/html"));
        _mime_type.insert(std::make_pair(".css", "text/css"));
        _mime_type.insert(std::make_pair(".js", "application/x-javascript"));
        _mime_type.insert(std::make_pair(".png", "image/png"));
        _mime_type.insert(std::make_pair(".jpg", "image/jpeg"));
        _mime_type.insert(std::make_pair(".unknown", "text/html")); // 不知道的默认转.html对应的

        _code_to_desc.insert(std::make_pair(100, "Continue"));
        _code_to_desc.insert(std::make_pair(200, "OK"));
        _code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
        _code_to_desc.insert(std::make_pair(302, "Found"));
        _code_to_desc.insert(std::make_pair(404, "Not Found"));
        _code_to_desc.insert(std::make_pair(500, "Internal Server Error"));
    }
    ~HttpServer()
    {
    }
    // 处理请求文件资源
    std::string ReadFileContent(const std::string &filepath, int *filesize)
    {
        // 1. 打开文件
        std::ifstream in(filepath.c_str(), std::ios::binary); // 以二进制方式读取
        if (!in.is_open())
        {
            return std::string();
        }
        // 2. 获取文件大小
        in.seekg(0, in.end);
        int size = in.tellg();
        in.seekg(0, in.beg);

        // 读取文件内容
        std::string filecontent;
        filecontent.resize(size);
        in.read((char *)filecontent.c_str(), size);
        in.close();
        *filesize = size;
        return filecontent;
    }
    void AddHandler(const std::string &functionname, func_t func)
    {
        std::string fname = wwwroot + functionname; // wwwroot/login
        _funcs[fname] = func;
    }
    std::string ProveCookieWrite()
    {
        return "Set-Cookie: username=zhangsan;";
    }
    std::string GetWeekDayName(int day)
    {
        std::vector<std::string> weekdays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        return weekdays[day];
    }
    std::string GetMonthName(int month)
    {
        std::vector<std::string> months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        return months[month];
    }
    std::string ExpireTimeUseRfc1123(int t)
    {
        time_t timeout = time(nullptr) + t; // 当前时间撮+Cookie生命周期==未来哪个时刻过期
        struct tm *tm = gmtime(&timeout);    // UTC未加时区的世界时间  gmtime函数将未来时刻转换为结构体存储了哪个时刻处于那天那时的信息
        char timebuffer[1024];
        // 时间格式：expires=Thu, 18 Dec 2024 12:00:00 UTC
        snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC",
                 GetWeekDayName(tm->tm_wday).c_str(),
                 tm->tm_mday,
                 GetMonthName(tm->tm_mon).c_str(),
                 tm->tm_year + 1900,
                 tm->tm_hour,
                 tm->tm_min,
                 tm->tm_sec);
        return timebuffer;
    }
    // #define TEST 1
    //   回调http服务
    std::string HandlerHttpReqeust(std::string &req)
    {
#ifdef TEST
        {
            std::cout << "---------------------------------------" << std::endl;
            std::cout << req;

            // http 的 client

            std::string response = "HTTP/1.0 200 OK\r\n"; // 404 NOT Found
            response += "\r\n";
            response += "<html><body><h1>hello world, hello bite!</h1></body></html>";
            return response;
        }
#else
        {
            std::cout << "---------------------------------------" << std::endl;
            std::cout << req;
            auto request = Factory::BuildHttpRequest();
            request->Deserialize(req);
            // request->Print();
            //  反序列化后, 将文件资源读出来, 并将其大小读出来， 等会用来构建 "Content-Length" 报头属性
            //  并处理文件后缀转换类型 等会用来 构建 "Content-Type" 报头属性
            //  接下来，构建Response   status_line  报头行， 响应正文
            //  std::string newurl = "https://www.baidu.com/";
            //  // 如果请求的是重定向路径，就重定向到指定的新路径
            //  if (request->Path() == "wwwroot/redir")
            //  {
            //      code = 301;
            //      response->AddStatusLine(code, _code_to_desc[code]);
            //      response->AddHeaderLine("Location", newurl);
            //  }

            // 判断是否带参数args
            if (request->IsExec())
            {
                auto response = _funcs[request->Path()](request);
                // response->AddHeaderLine("Set-Cookie", "username=zhangsan;");   //添加Cookie数据并验证
                // response->AddHeaderLine("Set-Cookie", "username=zhangsan; path=/a/b;"); //验证只有在指定路径下htpp请求时才会携带Cookie数据
                // 验证指定未来哪个时刻cookie数据过期 这里60s 这里验证一分钟后过期
                response->AddHeaderLine("Set-Cookie", "username=zhangsan; expires=" + ExpireTimeUseRfc1123(60) + ";");
                // 可以写多条Cookie数据 上面username为一个Cookie  password也可以为一个Cookie
                //response->AddHeaderLine("Set-Cookie", "password=123456;");
                return response->Serialize();
            }
            else
            {
                auto response = Factory::BuildHttpResponse();
                int code = 0;
                code = 200;
                int filesize = 0;
                std::string resource = ReadFileContent(request->Path(), &filesize); // 把文件以二进制字符串全部读出来， 并输出型参数将文件大小带出来
                // 请求不存在的资源
                if (resource.empty())
                {
                    code = 404;
                    response->AddStatusLine(code, _code_to_desc[code]);
                    std::string text404 = ReadFileContent("wwwroot/404.html", &filesize);
                    response->AddHeaderLine("Content-Length", std::to_string(filesize));
                    response->AddText(text404);
                    response->AddHeaderLine("Content-Type", _mime_type[".html"]); //.html  -> text/html
                }
                else
                {
                    response->AddStatusLine(code, _code_to_desc[code]);
                    response->AddHeaderLine("Content-Length", std::to_string(filesize));
                    response->AddText(resource);
                    response->AddHeaderLine("Content-Type", _mime_type[request->Suffix()]); //.html  -> text/html
                }
                // response->AddHeaderLine("Set-Cookie", "username=zhangsan;");    //添加Cookie数据并验证
                // response->AddHeaderLine("Set-Cookie", "username=zhangsan; path=/a/b;"); //验证只有在指定路径下htpp请求时才会携带Cookie数据
                // 验证指定未来哪个时刻cookie数据过期, 这里60s 这里验证一分钟后过期
                response->AddHeaderLine("Set-Cookie", "username=zhangsan; expires=" + ExpireTimeUseRfc1123(60) + ";");
                // 可以写多条Cookie数据 上面username为一个Cookie  password也可以为一个Cookie
                //response->AddHeaderLine("Set-Cookie", "password=123456;");
                return response->Serialize();
            }
        }

#endif
    }

private:
    // 根据请求资源的文件后缀 http将其转换为识别类型
    std::unordered_map<std::string, std::string> _mime_type;
    std::unordered_map<int, std::string> _code_to_desc; // 根据状态码，转状态描述
    std::unordered_map<std::string, func_t> _funcs;     // 根据Path回调对应的业务处理，构建response返回
};
